#include <stdio.h>
#include <stdlib.h>
#include "Hw3Types.h"
#include "Hw3TypesExtended.h"
#include "IspImage.h"
#include "TestFunctions.h"
#include "LoadingModule.h"
#include "SyncAndProtectionModule.h"
#include "IspImageAnalysisExtended.h"
#include "FindFacesModule.h"
#include "Output.h"
#include "FacebookModule.h"


//global variables declarations
HANDLE *hReadEvents = NULL;
TImageReaderAndProperties ImageReaderAndProperties;
TBoxSize FaceSize;
IspImage Image = NULL;
TFace *FacebookHead=NULL;
int MaximumNumberOfOffPixels;
HANDLE hAddFaceToFacebookMutex;
HANDLE hNumOfRunningThreadsSemaphore;
HANDLE OpenThreadsCounterMutex;
HANDLE hTerminateProgram;
int OpenThreadsCounter;
//end of global variables declarations


void main(int argc, char *argv[])
{
	/*oOoOoOoOoOoOoOoOoOoOoOoOooOoOoOoOoOoO*/
	//		Variables decleration
	/*oOoOoOoOoOoOoOoOoOoOoOoOoOoOooOoOoOoO*/

	char *ImageFileName = argv[1];
	char *OutputFileName = argv[2];
	int NumOfAreasVertical = atoi(argv[5]);
	int NumOfAreasHorizontal = atoi(argv[6]);
	int MaxNumOfParallelThreads = atoi(argv[7]);
	int NumOfAreas;
	char *EndReasonFileName = "End.txt";
	HANDLE LoadThread;
	TArea *Areas = NULL; 
	HANDLE *AreaThreadsHandles = NULL;
	int i;
	/*oOoOoOoOoOoOoOoOoOoOoOoOooOoOoOoOoOoO*/
	//	End of Variables decleration
	/*oOoOoOoOoOoOoOoOoOoOoOoOoOoOooOoOoOoO*/


	/*oOoOoOoOoOoOoOoOoOoOoOoOooOoOoOoOoOoO*/
	//	Global Variables initialization
	/*oOoOoOoOoOoOoOoOoOoOoOoOoOoOooOoOoOoO*/
	// Initialize global variables. these do not change the entire program, so no protection is needed
	FaceSize.Width = atoi(argv[3]);
	FaceSize.Height = atoi(argv[4]);
	
	// calculate the maximum number of pixels that can be different between two faces
	MaximumNumberOfOffPixels = min((FaceSize.Height+FaceSize.Width) , (FaceSize.Height*FaceSize.Width/8));
	
	// Initialize output files and mutexes. 
	// If this fails, we can't even write to the output files, so a message to the screen is printed
	if (InitializeOutputs(OutputFileName, EndReasonFileName) == FALSE)
	{
		printf("Can't initialize output files. The program will shut down.\n");
		goto Clean1;
	}

	// Initialize ImageReaderAndProperties
	if (OpenImageAndGetProperties(ImageFileName) == FALSE)
	{
		PrintErrorAndTerminate(ERROR_GETTING_IMAGE_PROPERTIES);
		goto Clean1;
	}
	
	// Create the image
	Image = CreateImage(ImageReaderAndProperties.Height, ImageReaderAndProperties.Width);
	if (Image == NULL) 
	{ 
		PrintErrorAndTerminate(ERROR_ALLOCATING_MEMORY);
		goto Clean2;
	}

	// Create the open threads counter mutex. This mutex protects the counter in case many threads tries to access 
	// it at once
	OpenThreadsCounterMutex = CreateMutex(NULL, FALSE, NULL);
	if (OpenThreadsCounterMutex == NULL)
	{
		PrintErrorAndTerminate(ERROR_CREATING_MUTEX);
		goto Clean3;
	}

	// Initialize the counter. At this point, no threads are running, so we don't have to use protected write.
	OpenThreadsCounter = 0;

	// Create the face book mutex. This mutex protects the face book in case many threads tries to add faces to  
	// the face book at once
	hAddFaceToFacebookMutex = CreateMutex (NULL, FALSE, NULL);
	if (hAddFaceToFacebookMutex == NULL)
	{
		PrintErrorAndTerminate(ERROR_CREATING_MUTEX);
		goto Clean4;
	}

	// Create the NumOfRunningThreadsSemaphore semaphore. This semaphore allows us to control the number of running  
	// threads at each given moment. 
	hNumOfRunningThreadsSemaphore = CreateSemaphore(NULL,MaxNumOfParallelThreads,MaxNumOfParallelThreads,NULL);
	if (hNumOfRunningThreadsSemaphore == NULL)
	{
		PrintErrorAndTerminate(ERROR_CREATING_SEMAPHORE);
		goto Clean5;
	}
	
	/*oOoOoOoOoOoOoOoOoOoOoOoOooOoOoOoOoOoO*/
	//	End of Global Variables init.
	/*oOoOoOoOoOoOoOoOoOoOoOoOoOoOooOoOoOoO*/

	/*oOoOoOoOoOoOoOoOoOoOoOoOooOoOoOoOoOoO*/
	//	Local Variables initialization
	/*oOoOoOoOoOoOoOoOoOoOoOoOoOoOooOoOoOoO*/
	NumOfAreas = NumOfAreasHorizontal*NumOfAreasVertical;
	
	// divide the picture to areas and store the areas in an array
	Areas = DividePicToAreas(NumOfAreasHorizontal, NumOfAreasVertical); 
	if (Areas == NULL)
	{	
		goto Clean6;
	}
	
	// create an event for each line of the picture. when the line is loaded, the event is set. This allows
	// us to control when each searching thread starts running - only when enough lines have finished loading
	hReadEvents = (HANDLE*)malloc(sizeof(HANDLE)*ImageReaderAndProperties.Height);
	if (hReadEvents == NULL)
	{
		PrintErrorAndTerminate(ERROR_ALLOCATING_MEMORY);
		goto Clean7;
	}
	if (CreateReadEvents() == FALSE)
	{
		goto Clean8;
	}
	/*oOoOoOoOoOoOoOoOoOoOoOoOooOoOoOoOoOoO*/
	//	End of Local Variables init.
	/*oOoOoOoOoOoOoOoOoOoOoOoOoOoOooOoOoOoO*/
	
	/*oOoOoOoOoOoOoOoOoOoOoOoOooOoOoOoOoOoO*/
	//	Run face search, compare etc.
	/*oOoOoOoOoOoOoOoOoOoOoOoOoOoOooOoOoOoO*/
	// The "open threads conter" is increased every time a thread is created. If creation fails, the counter 
	// is decreased again. If it succeeds, the thread itself is responsible to decrease the counter when it's finished.
	IncreaseOpenThreadsCounter();
	// create a loading thread to load the picture. this thread will also notify that 
	// lines have been loaded via hReadEvents.
	LoadThread = CreateThreadSimple((LPTHREAD_START_ROUTINE)ConvertImageToGrayScale, (LPVOID)Image, NULL);
	if (LoadThread == NULL)
	{
		PrintErrorAndTerminate(ERROR_CREATING_THREAD);
		DecreaseOpenThreadsCounter();
		goto Clean8;
	}
	
	// Create the area threads handles array
	AreaThreadsHandles = (HANDLE*)malloc(NumOfAreas*sizeof(HANDLE));
	if (AreaThreadsHandles == NULL)	
	{
		PrintErrorAndTerminate(ERROR_ALLOCATING_MEMORY);
		goto WaitForAllThreadsToFinish;
	}

	// Create the searching threads
	for (i=0 ; i < NumOfAreas ; i++)
	{
		IncreaseOpenThreadsCounter();
		AreaThreadsHandles[i] = CreateThreadSimple((LPTHREAD_START_ROUTINE)AreaThread, (LPVOID)&(Areas[i]), NULL);
		if (AreaThreadsHandles[i] == NULL) 
		{ 
			PrintErrorAndTerminate(ERROR_CREATING_THREAD);
			DecreaseOpenThreadsCounter();
			goto WaitForAllThreadsToFinish;
		}
	}

WaitForAllThreadsToFinish:
	// This is out mechanism to keep the main thread from exiting before all other threads have finished
	// Only when all other threads have finished will the counter reach zero, and the wait will be over
	while (OpenThreadsCounter != 0)
		Sleep(500);

	// If no errors have occured during the run and we have reached here, the program ended successfully. 
	// However, if an error has occured, the function WriteEndFile will not write anything to the EndFile 
	// since it is protected for a single write to it.
	WriteEndFile("Program ended successfully!"); 

	#define PRINT_FACES
	#ifdef PRINT_FACES
		PrintFacebook();
	#endif


	/*oOoOoOoOoOoOoOoOoOoOoOoOooOoOoOoOoOoO*/
	//				Clean up
	/*oOoOoOoOoOoOoOoOoOoOoOoOoOoOooOoOoOoO*/
	// Close the handles to the area threads
	CleanFacebook();
	for(i=0 ; i<NumOfAreas ; i++)
	{
		if (AreaThreadsHandles[i] != NULL)
			CloseHandle(AreaThreadsHandles[i]);
	}

	// Free the memory allocated to the area threads handles array
	free(AreaThreadsHandles);

	//Close the handle to the area thread
	CloseHandle(LoadThread);

	//Close all resources
Clean8:
		CloseReadEvents();
		free(hReadEvents);
Clean7:
		free(Areas);
Clean6:
		CloseHandle(hNumOfRunningThreadsSemaphore);
Clean5:
		CloseHandle(hAddFaceToFacebookMutex);
Clean4:
		CloseHandle(OpenThreadsCounterMutex);
Clean3:
		FreeImage(Image);
Clean2:
		CloseImageReaderAndProperties();
Clean1:
		FreeOutputs();

#ifdef DEBUG_MODE_VERBOSE
		printf("finished cleanning\n");
#endif
}
